package com.kuaiyin.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kuaiyin.dto.JMeterTestDTO;
import com.kuaiyin.entity.JmeterCaseRelation;
import com.kuaiyin.entity.JmeterTest;
import com.kuaiyin.entity.Request;
import com.kuaiyin.entity.TestPlan;
import com.kuaiyin.enums.JmeterTypeEnum;
import com.kuaiyin.enums.Status;
import com.kuaiyin.exception.BaseException;
import com.kuaiyin.mapper.JmeterCaseRelationMapper;
import com.kuaiyin.mapper.JmeterTestMapper;
import com.kuaiyin.mapper.RequestMapper;
import com.kuaiyin.mapper.TestPlanMapper;
import com.kuaiyin.service.IJmeterTestService;
import com.kuaiyin.util.JMeterExecutorUtil;
import com.kuaiyin.util.JMeterTestBuilder;
import com.kuaiyin.util.JmxVariableConverter;
import com.kuaiyin.util.UrlUtils;
import com.kuaiyin.utils.JsonVariableExtractorUtils;
import com.kuaiyin.utils.PageHelper;
import com.kuaiyin.utils.PageResult;
import com.kuaiyin.utils.RedisUtils;
import com.kuaiyin.vo.JMeterTestVO;
import com.kuaiyin.vo.TestCaseVO;
import com.kuaiyin.vo.TestPlanCaseVO;
import com.kuaiyin.vo.TestPlanVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * <p>
 * JMeter测试配置表 服务实现类
 * </p>
 *
 * @author ng
 * @since 2025-06-23
 */
@Slf4j
@Service
public class JmeterTestServiceImpl extends ServiceImpl<JmeterTestMapper, JmeterTest> implements IJmeterTestService {

    @Autowired
    private JmeterTestMapper jmeterTestMapper;
    @Autowired
    private TestPlanMapper testPlanMapper;
    @Autowired
    private JmeterCaseRelationMapper jmeterCaseRelationMapper;
    @Autowired
    private RequestMapper requestMapper;
    @Autowired
    private TestPlanServiceImpl testPlanService;
    @Autowired
    private TestCaseServiceImpl testCaseService;
    @Autowired
    private RedisUtils redisUtils;

    private static final String JMX_REDIS_KEY_PREFIX = "jmx:";   // JMX缓存的key前缀, 用于缓存JMX文件
    private static final String JMX_REPORT_KEY_PREFIX = "jmeter:report:";   // 压测报告缓存的key前缀, 用于缓存压测报告

    @Override
    @Transactional
    public void saveJmeterTest(JMeterTestDTO dto) {
        // 创建一个新的JmeterTest对象
        JmeterTest entity = new JmeterTest();

        // 检查DTO的类型是否为JMX
        if (dto.getType() == JmeterTypeEnum.JMX) {
            // 验证JMX内容，不进行特定的校验
            validateJmxContent(dto, false);
            // 将DTO的属性复制到实体对象中
            BeanUtils.copyProperties(dto, entity);
            // 设置实体对象的类型
            entity.setType(dto.getType().name());
            // 插入实体对象到数据库
            jmeterTestMapper.insert(entity);

            // 构造Redis键名
            String key = JMX_REDIS_KEY_PREFIX + entity.getId();
            // 将JMX内容存储到Redis中
            redisUtils.set(key, dto.getJmxContent());
        } else {
            // 验证简单的计划
            validateSimplePlan(dto.getPlanId());
            // 选择并获取计划名称
            String planName = testPlanMapper.selectName(dto.getPlanId());
            // 将计划名称设置到DTO中
            dto.setName(planName);

            // 映射线程组，将DTO和实体对象关联起来
            mapThreadGroup(dto, entity, planName);
            // 将DTO的属性复制到实体对象中
            BeanUtils.copyProperties(dto, entity);
            // 设置实体对象的类型
            entity.setType(dto.getType().name());

            // 插入实体对象到数据库
            jmeterTestMapper.insert(entity);
            // 处理响应提取
            handleResponseExtract(Long.valueOf(entity.getId()), dto.getPlanId(), dto.getResponseExtract());
        }
    }

    @Override
    @Transactional
    public void updateJmeterTest(Long id, JMeterTestDTO dto) {
        dto.setId(id);
        JmeterTest entity = new JmeterTest();
        entity.setId(Math.toIntExact(id));

        if (dto.getType() == JmeterTypeEnum.JMX) {
            validateJmxContent(dto, true);
            BeanUtils.copyProperties(dto, entity);
            entity.setType(dto.getType().name());

            jmeterTestMapper.updateById(entity);
            String key = JMX_REDIS_KEY_PREFIX + id;
            redisUtils.set(key, dto.getJmxContent());
        } else {
            validateSimplePlan(dto.getPlanId());
            String planName = testPlanMapper.selectName(dto.getPlanId());
            dto.setName(planName);

            mapThreadGroup(dto, entity, planName);
            BeanUtils.copyProperties(dto, entity);
            entity.setType(dto.getType().name());

            jmeterTestMapper.updateById(entity);
            jmeterCaseRelationMapper.deleteByJmeterId(id);
            handleResponseExtract(id, dto.getPlanId(), dto.getResponseExtract());
        }
    }


    @Override
    public JMeterTestVO getById(Long id) {
        JmeterTest jmeterTest = super.getById(id);
        JMeterTestVO jmeterTestVO = new JMeterTestVO();
        BeanUtils.copyProperties(jmeterTest, jmeterTestVO);
        //1、根据测试计划id获取响应提取配置
        List<Map<String, Object>> result = jmeterCaseRelationMapper.selectResponseExtractByJmeterId(id);
        // 2、组装成 Map<String, Map<String, String>>
        Map<String, Map<String, String>> responseExtractMap = result.stream()
                .collect(Collectors.toMap(
                        map -> map.get("case_id").toString(),
                        map -> {
                            String json = (String) map.get("jmx_response_extract");
                            return JSONUtil.toBean(json, Map.class);
                        }
                ));
        // 3、设置到 VO 中（假设 VO 中有这个字段）
        jmeterTestVO.setResponseExtract(responseExtractMap);
        return jmeterTestVO;
    }

    @Override
    public PageResult<JMeterTestVO> getJmeterTest(Integer page, Integer
            size, Consumer<LambdaQueryWrapper<JmeterTest>> condition) {
        return PageHelper.queryWithVo(page, size, jmeterTestMapper, JMeterTestVO.class, condition);
    }

    @Override
    public List<JMeterTestVO> getJmeterTestList() {
        return PageHelper.queryAllWithVo(jmeterTestMapper, JMeterTestVO.class);
    }

    @Override
    public void removeById(Long id) {
        super.removeById(id);
        //1、校验key是否存在
        if (redisUtils.exists(JMX_REDIS_KEY_PREFIX + id)) {
            redisUtils.del(JMX_REDIS_KEY_PREFIX + id);
        }
        if (redisUtils.exists(JMX_REPORT_KEY_PREFIX + id)) {
            redisUtils.hDel(JMX_REPORT_KEY_PREFIX + id);
        }
    }

    @Override
    public void runJmeterTest(Long id) throws Exception {
        //1、获取测试类型
        JmeterTypeEnum type = JmeterTypeEnum.valueOf(super.getById(id).getType());
        //2、根据类型执行对应的方法
        switch (type) {
            case SIMPLE:
                runJmeter(id);
                break;
            case JMX:
                runJmx(id);
                break;
        }
    }

    @Override
    public Map<String, String> getReport(Long id) {
        String redisKey = JMX_REPORT_KEY_PREFIX + id;
        // 1、判断是否有报告
        if (!redisUtils.exists(redisKey)) {
            throw BaseException.of(0, "该用例的报告不存在，请重新生成！");
        }
        // 2、获取所有hash条目
        Map<String, String> allEntries = redisUtils.getHashEntriesAsStringMap(redisKey);
        // 3、按时间戳排序并取最新的10条
        return allEntries.entrySet().stream()
                .sorted(Map.Entry.<String, String>comparingByKey().reversed()) // 按key(时间戳)降序排序
                .limit(10) // 限制10条
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue,
                        LinkedHashMap::new // 保持插入顺序
                ));
    }

    @Override
    public void stopTestPlan(Long id) {
        String executionId = "jmeter_run_" + id;
        JMeterExecutorUtil.stopJmxExecution(executionId);
    }

    /**
     * 运行JMX
     *
     * @param id 需要有测试计划ID
     *           这里是直接拿着jmx的内容跑运行
     */
    public void runJmx(Long id) throws Exception {
        //1、拿redis数据
        String key = JMX_REDIS_KEY_PREFIX + id;
        String buildAndSaveJmx = (String) redisUtils.get(key);
        //2、调用执行方法
        String executionId = "jmeter_run_" + id;  // 执行ID
        Future<Map<String, Object>> future = JMeterExecutorUtil.executeJmxAsync(executionId,buildAndSaveJmx); //异步执行JMX
        Map<String, Object> result = future.get();
        String jsonStr = JSONUtil.toJsonStr(result);
        //11、写入报告redis
        String reporeKey = JMX_REPORT_KEY_PREFIX + id;
        long seconds = cn.hutool.core.date.DateUtil.current() / 1000;
        redisUtils.hSet(reporeKey, String.valueOf(seconds), jsonStr);
    }


    /**
     * 异步执行JMX,运行的是自定义的 JMeter 引擎
     *
     * @param id 压测计划ID
     * @throws Exception 异常
     */
    public void runJmeter(Long id) throws Exception {
        //1、获取到关联的测试计划id
        JmeterTest jmeterTest = super.getById(id);
        Long planId = jmeterTest.getPlanId();
        //2、校验关联的测试计划id是否存在或者禁用.
        TestPlan testPlan = testPlanMapper.selectById(planId);
        if (testPlan == null || testPlan.getStatus() == Status.NORMAL) {
            throw BaseException.of(0, "计划不存在或者已禁用");
        }
        // 3. 获取测试计划详情
        TestPlanVO testPlanVO = testPlanService.getById(planId);
        // 4. 获取关联的测试用例列表
        List<TestPlanCaseVO> cases = testPlanVO.getCases();
        if (cases == null || cases.isEmpty()) {
            throw BaseException.of(0, "该计划没有关联任何测试用例");
        }
        // 5. 按执行顺序排序
        List<Long> caseIds = cases.stream()
                .sorted(Comparator.comparingInt(TestPlanCaseVO::getExecutionOrder))
                .map(TestPlanCaseVO::getCaseId)
                .toList();
        // 新增字段：用于保存所有HTTP请求配置
        List<JMeterTestDTO.HttpRequestConfig> httpRequestConfigs = new java.util.ArrayList<>();
        //6、获取单个用例调用生成jmx
        for (Long caseId : caseIds) {
            //6.1 获取用例详情
            TestCaseVO testCaseVO = testCaseService.getById(caseId);
            String environmentType = testCaseVO.getEnvironmentType();
            // 6.2. 获取环境变量（从 Redis 中获取全局变量和提取变量）
            Map<String, String> envMap = getEnvironmentVariables(environmentType, id);
            //6.4. 获取请求数据
            Request request = requestMapper.selectById(testCaseVO.getRequestId());
            //6. 5. 构建最终 URL（替换路径中的 {{变量}}）
            String baseUrl = buildUrl(request.getPath(), envMap);
            Map<String, Object> urlInfo = UrlUtils.parseUrlInfo(baseUrl);
            String protocol = (String) urlInfo.get("protocol");
            String domain = (String) urlInfo.get("domain");
            int port = (Integer) urlInfo.get("port");
            String path = (String) urlInfo.get("path");
            //6.6、替换请求头和请求体中的变量，处理成jmx可读的${token}
            String headers = JmxVariableConverter.convertTemplate(request.getHeaders().toString());
            Map<String, String> headersMap = JSONUtil.toBean(headers, new TypeReference<>() {
            }, true);
            String body = JmxVariableConverter.convertTemplate(request.getBody().toString());
            Map<String, String> bodyMap = JSONUtil.toBean(body, new TypeReference<>() {
            }, true);
            //6.7、获取请求方式
            String method = request.getMethod();
            //6.8、获取响应提取表达式
            String jmxResponseExtract = jmeterCaseRelationMapper.selectJmxResponseExtract(id, caseId, planId);
            Map<String, Object> jmxResponseExtractMap = JSONUtil.toBean(jmxResponseExtract, new TypeReference<>() {
            }, true);
            //6.8 构建 HttpRequestConfig 对象并加入列表
            JMeterTestDTO.HttpRequestConfig config = JMeterTestDTO.HttpRequestConfig.builder()
                    .name(request.getName()).
                    protocol(protocol).
                    domain(domain).
                    port(port).
                    path(path).
                    method(method).
                    extractVariables(jmxResponseExtractMap).
                    headers(headersMap).
                    followRedirects(true).
                    build();
            if (body != null && !body.isEmpty()) {
                if (headers.contains("application/x-www-form-urlencoded")) {
                    config.setParameters(bodyMap);
                } else {
                    Map<String, Object> safeBodyMap = bodyMap.entrySet()
                            .stream()
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
                    config.setBody(safeBodyMap);
                }
            }
            //6.9、加入列表
            httpRequestConfigs.add(config);
        }
        //7、构建线程组配置
        JMeterTestDTO.ThreadGroupConfig threadGroup = JMeterTestDTO.ThreadGroupConfig.builder()
                .name(jmeterTest.getName())
                .threads(jmeterTest.getThreads())
                .rampUp(jmeterTest.getRampUp())
                .loops(jmeterTest.getLoops())
                .scheduler(jmeterTest.getScheduler())
                .duration(jmeterTest.getDuration())
                .delayedStart(jmeterTest.getDelayedStart()).build();

        // 8. 构建完整测试 DTO
        JMeterTestDTO dto = JMeterTestDTO.builder()
                .name(jmeterTest.getName())
                .threadGroup(threadGroup)
                .httpRequests(httpRequestConfigs)
                .build();
        String buildAndSaveJmx = JMeterTestBuilder.buildAndSaveJmx(dto);//生成的jmx内容
        //9、写入redis
        String key = JMX_REDIS_KEY_PREFIX + id;
        redisUtils.set(key, buildAndSaveJmx);
        //10、调用执行方法
        String executionId = "jmeter_run_" + id;
        Future<Map<String, Object>> future = JMeterExecutorUtil.executeJmxAsync(executionId,buildAndSaveJmx); //异步执行JMX
        Map<String, Object> result = future.get();
        String jsonStr = JSONUtil.toJsonStr(result);
        //11、写入报告redis
        String reporeKey = JMX_REPORT_KEY_PREFIX + id;
        long seconds = cn.hutool.core.date.DateUtil.current() / 1000;
        redisUtils.hSet(reporeKey, String.valueOf(seconds), jsonStr);
    }

    /**
     * 获取环境变量（包括全局变量和提取变量）
     *
     * @param environmentType 环境类型（如 dev）
     * @param id              请求唯一标识
     * @return 环境变量 Map
     */
    private Map<String, String> getEnvironmentVariables(String environmentType, Long id) {
        Map<String, String> envMap = redisUtils.getHashEntriesAsStringMap("environment:" + environmentType);
        envMap.putAll(redisUtils.getHashEntriesAsStringMap("testPlan_id_" + id + "_responseExtract_" + environmentType));
        return envMap;
    }
// ==================================     私有方法封装    ==================================

    /**
     * 构建最终 URL，替换路径中的 {{变量名}} 为实际值
     *
     * @param path   原始路径（可能包含变量）
     * @param envMap 环境变量 Map
     * @return 替换后的 URL
     */
    protected String buildUrl(String path, Map<String, String> envMap) {
        if (path == null || !path.contains("{{")) return path;

        Set<String> variables = JsonVariableExtractorUtils.extractVariablesAndFunctions(path);
        for (String var : variables) {
            String value = envMap.get(var);
            if (value == null) {
                throw BaseException.of(0, "URL上的环境变量不存在：" + var);
            }
            path = path.replaceAll("\\{\\{" + var + "}}", value);
        }
        return path;
    }

    private void validateJmxContent(JMeterTestDTO dto, boolean isUpdate) {
        if (StringUtils.isBlank(dto.getJmxContent())) {
            throw BaseException.of(0, "JMX文件不能为空");
        }
        if (StringUtils.isBlank(dto.getName())) {
            throw BaseException.of(0, "压测计划名称不能为空");
        }
        boolean exists = lambdaQuery()
                .eq(JmeterTest::getName, dto.getName())
                .ne(isUpdate, JmeterTest::getId, dto.getId())
                .exists();
        if (exists) {
            throw BaseException.of(0, "计划名称已存在");
        }
    }

    private void validateSimplePlan(Long planId) {
        if (planId == null || StringUtils.isBlank(testPlanMapper.selectName(planId))) {
            throw BaseException.of(0, "关联的测试计划ID不存在");
        }
    }

    private void mapThreadGroup(JMeterTestDTO dto, JmeterTest entity, String planName) {
        if (dto.getThreadGroup() != null) {
            entity.setThreadGroupName(planName);
            entity.setThreads(dto.getThreadGroup().getThreads());
            entity.setRampUp(dto.getThreadGroup().getRampUp());
            entity.setLoops(dto.getThreadGroup().getLoops());
            entity.setScheduler(dto.getThreadGroup().isScheduler());
            entity.setDuration(dto.getThreadGroup().getDuration());
            entity.setDelayedStart(dto.getThreadGroup().getDelayedStart());

            if (dto.getThreadGroup().isScheduler() && dto.getThreadGroup().getLoops() != -1) {
                throw BaseException.of(0, "调度器仅允许循环次数为-1");
            }
        }
    }

    private void handleResponseExtract(Long jmeterId, Long planId, Map<String, Object> extractMap) {
        if (extractMap != null && !extractMap.isEmpty()) {
            for (Map.Entry<String, Object> entry : extractMap.entrySet()) {
                String caseId = entry.getKey();
                Map<Object, Object> valueMap = MapUtil.get(extractMap, caseId, Map.class);

                JmeterCaseRelation relation = new JmeterCaseRelation();
                relation.setJmeterId(jmeterId);
                relation.setPlanId(planId);
                relation.setCaseId(Long.valueOf(caseId));
                relation.setJmxResponseExtract(valueMap);

                jmeterCaseRelationMapper.insert(relation);
            }
        }
    }


}
